package cn.sc.summer.gateway.webfilter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.sc.summer.constant.gateway.GatewayConstant;
import cn.sc.summer.constant.model.Result;
import cn.sc.summer.constant.token.AuthProperties;
import cn.sc.summer.constant.warmup.WarmUpConstant;
import cn.sc.summer.gateway.util.IPUtil;
import cn.sc.summer.redis.util.IpLimitUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 本类为SpringCloud Gateway中设置允许跨域请求的过滤器。
 * SpringGateWay是基于WebFlux的，所以普通过滤器就算加上@Order优先级也不够
 * 所以我们使用WebFilter，单独一个Component，加上@Order就起作用了
 */
@Slf4j
public class CorsWebFilter implements WebFilter {

    private final AuthProperties authProperties;

    public CorsWebFilter(AuthProperties authProperties) {
        this.authProperties = authProperties;
    }

    @Override
    @NonNull
    public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
        exchange.getAttributes().put(GatewayConstant.GATEWAY_START_TIME, System.currentTimeMillis());
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getPath().value();
        String ipAddr = IPUtil.getIpAddr(request);

        if (CharSequenceUtil.isNotBlank(path) && path.equals(WarmUpConstant.WARM_UP)) {
            response.setStatusCode(HttpStatus.OK);
            DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(Result.success()).getBytes());
            return response.writeWith(Mono.just(buffer));
        }

        final String errorMsg = "Sorry, Your IP address has been locked！";
        List<String> blackIpList = Arrays.stream(authProperties.getBlack()).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(blackIpList) && blackIpList.contains(ipAddr)) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(Result.fail(errorMsg)).getBytes());
            log.error("==> {}", errorMsg);
            return response.writeWith(Mono.just(buffer));
        }

        if (Boolean.FALSE.equals(IpLimitUtil.checkBlackList(ipAddr))) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(Result.fail(errorMsg)).getBytes());
            log.error("==> {}", errorMsg);
            return response.writeWith(Mono.just(buffer));
        }

        if (Boolean.FALSE.equals(IpLimitUtil.checkBlackList(ipAddr, path))) {
            response.setStatusCode(HttpStatus.FORBIDDEN);
            DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(Result.fail(errorMsg)).getBytes());
            log.error("==> {}", errorMsg);
            return response.writeWith(Mono.just(buffer));
        }

        if (CorsUtils.isCorsRequest(request)) {
            HttpHeaders requestHeaders = request.getHeaders();
            HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
            HttpHeaders headers = response.getHeaders();
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
            headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders
                    .getAccessControlRequestHeaders());
            if (requestMethod != null) {
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
            }
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
            headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
            headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "18000");
            if (Objects.equals(request.getMethod(), HttpMethod.OPTIONS)) {
                response.setStatusCode(HttpStatus.OK);
                DataBuffer buffer = response.bufferFactory().wrap(JSON.toJSONString(Result.success()).getBytes());
                return response.writeWith(Mono.just(buffer));
            }
        }
        return chain.filter(exchange);
    }

}
